/* GameLogic.java
 * Implements the GameLogic class
 * The class represents a 4-dimensional tic-tac-toe board
 * Objects of the class contain the game state, as well as the game
 * history. 
 */
package tictactoe;

public class GameLogic {
	//contains the current state of the game
	// can return or update current state of the game as requested
	
	public static final int num_dimensions = 4; //This game implements a four-dimensional game
	public enum square {X,O,Empty}; //used internally to track which markers are in the square
   
	/* The class is currently used for tic-tac-toe boards of length 4.
	 * The default constructor sets board_length to 4
	 */
	public GameLogic(GameStatus g) {
		board_length = 4; // 4 is the default board length
		moves = new MoveList();
                gamestatus=g;
		reset();
	}
	
	/* Another constructor was provided to create boards of varying dimensions so a
	 * board of different dimensions could be developed.
	 */
	public GameLogic(int board_length) {
		this.board_length = board_length;
		moves = new MoveList();
		reset();
	}
	
	/* This constructor is used to make a copy of the current gamelogic class when
	 * calling the newInstance method.
	 */
	public GameLogic(int board_length, Marker[][][][] board, boolean x_turn
			, MoveList moves) {
		this.board_length = board_length;
		this.board =new Marker[board_length][board_length][board_length][board_length];
		for(int coord1 = 0; coord1 < board_length; coord1++) {
			for(int coord2 = 0; coord2 < board_length; coord2++) {
				for(int coord3 = 0; coord3 < board_length; coord3++) {
					for(int coord4 = 0; coord4 < board_length; coord4++) {
						this.board[coord1][coord2][coord3][coord4] = board[coord1][coord2][coord3][coord4];
					}
				}
			}
		}
		this.x_turn = x_turn;
		this.moves = moves.newInstance();
	}
	
	/* returns a copy of the calling GameLogic object
	 */
	public GameLogic newInstance() {
		return new GameLogic(board_length, board, x_turn, moves);
	}
	
	public void reset() {
		//Empties all squares, sets x to move first, and deletes the history of moves
		board = new Marker[board_length][board_length][board_length][board_length];
		for(int coord1 = 0; coord1 < board_length; coord1++) {
			for(int coord2 = 0; coord2 < board_length; coord2++) {
				for(int coord3 = 0; coord3 < board_length; coord3++) {
					for(int coord4 = 0; coord4 < board_length; coord4++) {
						board[coord1][coord2][coord3][coord4] = Marker.Empty;
					}
				}
			}
		}
		x_turn = true;
		moves.reset();
	}
	
	public Marker[][][][] gameState() {
		// returns a 4-dimensional array of squares that represent the game in it's current state
		// if show_threats is on, replace empty squares where people can win with a special marker
		
		//copy the state
		Marker[][][][] state = new Marker[board_length][board_length][board_length][board_length];
		for(int coord1 = 0; coord1 < board_length; coord1++) {
			for(int coord2 = 0; coord2 < board_length; coord2++) {
				for(int coord3 = 0; coord3 < board_length; coord3++) {
					for(int coord4 = 0; coord4 < board_length; coord4++) {
						state[coord1][coord2][coord3][coord4] = board[coord1][coord2][coord3][coord4];
					}
				}
			}
		}
		return state;		
	}
	
	// returns the length of the board's dimensions
	public int getLength() {
		return board_length;
	}
	
	public void setLength(int newlength) {
		// changes the board length and resets the board
		// reset is called in order to change the dimensions of the board
		// and to empty out all of the squares, as changing dimensions
		// of an existing board doesn't make sense
		board_length = newlength;
		reset();
	}
	
	public boolean makeMove(int w, int x, int y, int z) {
		// if the move is valid, modifies the board, adds it to the list
		// of moves, changes the turn and returns true to signify that the
		// move was made successfully
		// if move is out of range, or if square is already filled,
		// returns false to indicate that no move was made
		if(!in_range(w) || !in_range(x) || !in_range(y) || !in_range(z)) {
			return false;
		}
		if(board[w][x][y][z] != Marker.Empty) {
			return false;
		}
		Marker marker;
		if(x_turn){
			marker = Marker.X;
		}
		else
		{
			marker = Marker.O;
		}
		board[w][x][y][z] = marker;
		x_turn = !x_turn;
		moves.addMove(new Square(w,x,y,z));
		return true;
	}
	
	private boolean in_range(int coordinate) {
		// Returns true if and only if the integer given could be used to specify
		// a square on the board.
		return (coordinate >= 0 && coordinate < board_length);
	}
	
	// returns a boolean indicating whose turn it is. true for x, false for o
	public boolean get_x_turn() {
		return x_turn;
	}
	
	// tells whether X won the game, O won the game, the game is drawn, or the game is in progress

	public GameStatus winState() { 
		//returns a game_status enum that tells the outcome of the game so far
		if(player_wins(Marker.X)) return GameStatus.Xwin;
		if(player_wins(Marker.O)) return GameStatus.Owin;
		if(board_full()) return GameStatus.Draw;
		return GameStatus.InProgressSingle;
	}
	
	private boolean board_full() {
		// returns true if none of the squares on the board are empty
		for(int coord1 = 0; coord1 < board_length; coord1++) {
			for(int coord2 = 0; coord2 < board_length; coord2++) {
				for(int coord3 = 0; coord3 < board_length; coord3++) {
					for(int coord4 = 0; coord4 < board_length; coord4++) {
						if(board[coord1][coord2][coord3][coord4] == Marker.Empty) {
							return false;
						}
					}
				}
			}
		}
		return true;
	}

	// Tells the caller whether the indicated player won the game
	private boolean player_wins(Marker player) {
		// Loop through all rows. If a row contains only markers from player,
		// return true. Else, return false.
		Row r = new Row();
		do {
			boolean all_squares = true;
			for(int square_num = 0; square_num < board_length; square_num++)
			{
				Square current = r.getSquare(square_num, board_length);
				Marker curr_marker = board[current.coord1][current.coord2][current.coord3][current.coord4];
				if(player != curr_marker) {
					all_squares = false;
				}
			}
			if(all_squares) return true;
		} while(r.next(board_length));
		return false;
	}
	
	public void undo() {
		// if the list of moves has previous moves, undoes the last move
		// tells the list of moves that the last move is the current move
		Square last = moves.undo();
		if(last != null) {
			int coord1 = last.coord1;
			int coord2 = last.coord2;
			int coord3 = last.coord3;
			int coord4 = last.coord4;
			x_turn = !x_turn;
			board[coord1][coord2][coord3][coord4] = Marker.Empty;
		}

	}
	
	public void redo() {
		// if the list of moves has moves stored before the current move,
		// redo them and tell the list that the next move is now the current one.
		Square next = moves.redo();
		if(next != null) {
			int coord1 = next.coord1;
			int coord2 = next.coord2;
			int coord3 = next.coord3;
			int coord4 = next.coord4;
			Marker marker;
			if(x_turn) {
				marker = Marker.X;
			}
			else {
				marker = Marker.O;
			}
			board[coord1][coord2][coord3][coord4] = marker;
			x_turn = !x_turn;
		}
	}

	/* returns an array of all of the empty squares */
	public Square[] emptySquares() {
		/* count the empty squares, allocate space in the array,  and fill the array
		 * with the empty squares
		 */
		int num_empty = 0;
		for(int i = 0; i < board_length; i++) {
			for(int j = 0; j < board_length; j++) {
				for(int k = 0; k < board_length; k++) {
					for(int l = 0; l < board_length; l++) {
						if(board[i][j][k][l] == Marker.Empty) num_empty++;
					}
				}
			}
		}
		Square[] result = new Square[num_empty];
		int index = 0;
		for(int i = 0; i < board_length; i++) {
			for(int j = 0; j < board_length; j++) {
				for(int k = 0; k < board_length; k++) {
					for(int l = 0; l < board_length; l++) {
						if(board[i][j][k][l] == Marker.Empty) {
							result[index] = new Square(i,j,k,l);
							index++;
						}
					}
				}
			}
		}		
		return result;
	}
		
	private Marker[][][][] board; // stores the current state of the board
	private int board_length; // stores the board's length
	private boolean x_turn; // true if x is to move, false if o is to move
    private GameStatus gamestatus;
	// MoveList: list of all moves that made, including ones that have already been undone
	// keeps track of which moves are shown on board and which are not
	private MoveList moves;
}